<?php
	/**
	  * @package	MyDebuger
	  * @name		CombinationsBuilder
	  * @brief		Clase para generar combinaciones de una lista de elementos
	  * @author		Hermann D. Schimpf <hschimpf@gschimpf.com>
	  * @license	http://opensource.org/licenses/gpl-2.0.php
	  * @date		2009-12-31
	  * @version	0.1.5
  	**/

	class CombinationsBuilder extends MyDebuger implements MyVersions {
		/* version de la clase */
		private static $version			= 0;
		/* subversion de la clase */
		private static $subversion		= 1;
		/* numero de revision de la clase */
		private static $revision		= 5;

		/* elementos a procesar */
		private $elementsList		= Array();
		/* lista de posibles combinaciones */
		private $combinations		= Array();
		/* combinacion temporal para proceso */
		private $combination			= Array();
		/* cantidad de combinaciones posibles */
		private $combinationsCount	= 1;

		public function __construct($debug = False) {
			/* creamos el padre */
			parent::__construct($debug, __CLASS__);
		}

		public static final function getVersion() {
			/* retornamos la version actual de la clase */
			return 'v' . self::$version . '.' . self::$subversion . '.' . self::$revision;
		}

		public static function getCombinations($elementsList = Null, $debug = False) {
			try {
				try {
					/* creamos una instancia */
					$getCombinations = new CombinationsBuilder($debug);
					/* verificamos los valores recibidos */
					$getCombinations->verificarValoresRecibidos($elementsList);
					/* almacenamos los elementos recibidos */
					$getCombinations->saveElements($elementsList, True);
					/* almacenamos la cantidad de combinaciones que debemos realizar */
					$getCombinations->saveCombinationsCount();
					/* ejecutamos el proceso */
					$getCombinations->runProcess();
					/* retornamos las combinaciones */
					return $getCombinations->getResult();
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$getCombinations->convertToMyException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$getCombinations->show($e->getMensaje());
			}
		}

		private function verificarValoresRecibidos($elementsList) {
			/* mostramos un mensaje */
			$this->show('Verificando valores recibidos..', False);
			/* verificamos si especifico algun valor */
			if ($elementsList === Null)
				/* salimos con una excepcion */
				$this->newException('No se especificaron los elementos a procesar');
			/* verificamos si los elementos se pasaron por medio de un array */
			if (gettype($elementsList) !== 'array')
				/* salimos con una excepcion */
				$this->newException('Los elementos se deben enviar en un array');
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function saveElements($elementsList, $showMsg = False) {
			/* almacenamos los elementos recibidos */
			$this->elementsList = $elementsList;
			/* verificamos si mostramos un mensaje */
			if ($showMsg === True)
				/* mostramos un mensaje */
				$this->show('Cantidad de elementos almacenados: ' . $this->bold(count($this->elementsList)));
		}

		private function saveCombinationsCount() {
			/* mostramos un mensaje */
			$this->show('Obteniendo cantidad de combinaciones a crear..', False);
			/* ingresamos a un bucle */
			for ($i = 1; $i <= count($this->getElements()); $i++)
				/* almacenamos la cantidad de combinaciones posibles */
				$this->combinationsCount *= $i;
			/* mostramos ok al mensaje */
			$this->showOK();
			/* mostramos la cantidad de combinaciones obtenidas */
			$this->show('Cantidad de combinaciones a crear: ' . $this->bold($this->combinationsCount));
		}

		private function runProcess() {
			/* mostramos un mensaje */
			$this->show('Iniciando proceso de combinacion..');
			/* armamos las combinaciones */
			$this->armarCombinaciones();
			/* recorremos la cantidad de elementos */
			for ($i = 1; $i < count($this->getElements()); $i++) {
				/* mostramos un mensaje */
				$this->show('Cantidad de combinaciones obtenidas hasta el momento: ' . count($this->getResult()));
				/* verificamos si ya terminamos el proceso */
				if ($this->endProcess())
					/* salimos del bucle */
					break;
				/* recorremos las combinaciones */
				foreach ($this->getResult() AS $combination) {
					/* verificamos si ya terminamos el proceso */
					if ($this->endProcess())
						/* salimos del bucle */
						break;
					/* almacenamos la combinacion en orden inverso */
					$this->saveElements(array_reverse($combination));
					/* volvemos a ejecutar el proceso */
					$this->armarCombinaciones();
				}
			}
			/* mostramos la cantidad de combinaciones obtenidas */
			$this->show('Combinaciones obtenidas: ' . $this->bold(count($this->getResult())));
		}

		private function getElements() {
			/* retornamos la lista de elementos */
			return $this->elementsList;
		}

		private function armarCombinaciones() {
			/* recorremos cada uno de los elementos */
			foreach ($this->getElements() AS $element) {
				/* verificamos si ya terminamos el proceso */
				if ($this->endProcess())
					/* salimos del bucle */
					break;
				/* procesamos el elemento */
				$this->processElement($element);
			}
		}

		private function processElement($element) {
			/* recorremos todas las posiciones posibles */
			for ($pos = 0; $pos < count($this->getElements()); $pos++) {
				/* verificamos si ya terminamos el proceso */
				if ($this->endProcess())
					/* salimos del bucle */
					break;
				/* vaciamos el array temporal */
				$this->emptyTempCombination();
				/* almacenamos el elemento actual en la posicion en la que nos encontramos */
				$this->addElementInPos($element, $pos);
				/* agregamos los elementos restantes */
				$this->addMissingElements($element);
				/* ordenamos los indices de la combinacion */
				ksort($this->combination);
				/* verificamos si la combinacion ya la tenemos */
				if (!in_array($this->combination, $this->combinations))
					/* creamos una combinacion con el elemento actual */
					$this->combinations[] = $this->combination;
			}
		}

		private function emptyTempCombination() {
			/* vaciamos el array temporal */
			$this->combination = Array();
		}

		private function addElementInPos($element, $index) {
			/* almacenamos el elemento en la posicion */
			$this->combination[$index] = $element;
		}

		private function addMissingElements($element) {
			/* recorremos los elementos restantes */
			foreach (array_diff($this->getElements(), Array($element)) AS $missingElement) {
				/* recorremos todas las posiciones posibles */
				for ($posMissing = 0; $posMissing < count($this->getElements()); $posMissing++) {
					/* verificamos si la posicion ya tiene un elemento */
					if (!isset($this->combination[$posMissing])) {
						/* almacenamos el elemento faltante en la posicion */
						$this->addElementInPos($missingElement, $posMissing);
						/* finalizamos el buqle de posiciones */
						break;
					}
				}
			}
		}

		private function endProcess() {
			/* retornamos si el proceso finalizo */
			return (
				/* verificamos si la cantidad de combinaciones ya esta correcta */
				count($this->getResult()) == $this->combinationsCount ?
				/* retornamos True */
				True :
				/* retornamos False */
				False
			);
		}

		private function getResult() {
			/* retornamos las combinaciones */
			return $this->combinations;
		}
	}
?>